/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// "liveMedia"
// Copyright (c) 1996-2018 Live Networks, Inc.  All rights reserved.
// A server demultiplexer for a MPEG 1 or 2 Program Stream
// Implementation

#include "include/MPEG1or2FileServerDemux.hh"
#include "include/MPEG1or2DemuxedServerMediaSubsession.hh"
#include "include/ByteStreamFileSource.hh"

MPEG1or2FileServerDemux *
MPEG1or2FileServerDemux::createNew(UsageEnvironment &env, char const *fileName,
                                   Boolean reuseFirstSource) {
    return new MPEG1or2FileServerDemux(env, fileName, reuseFirstSource);
}

static float MPEG1or2ProgramStreamFileDuration(UsageEnvironment &env,
                                               char const *fileName,
                                               unsigned &fileSize); // forward
MPEG1or2FileServerDemux
::MPEG1or2FileServerDemux(UsageEnvironment &env, char const *fileName,
                          Boolean reuseFirstSource)
        : Medium(env),
          fReuseFirstSource(reuseFirstSource),
          fSession0Demux(NULL), fLastCreatedDemux(NULL), fLastClientSessionId(~0) {
    fFileName = strDup(fileName);
    fFileDuration = MPEG1or2ProgramStreamFileDuration(env, fileName, fFileSize);
}

MPEG1or2FileServerDemux::~MPEG1or2FileServerDemux() {
    Medium::close(fSession0Demux);
    delete[] (char *) fFileName;
}

ServerMediaSubsession *
MPEG1or2FileServerDemux::newAudioServerMediaSubsession() {
    return MPEG1or2DemuxedServerMediaSubsession::createNew(*this, 0xC0, fReuseFirstSource);
}

ServerMediaSubsession *
MPEG1or2FileServerDemux::newVideoServerMediaSubsession(Boolean iFramesOnly,
                                                       double vshPeriod) {
    return MPEG1or2DemuxedServerMediaSubsession::createNew(*this, 0xE0, fReuseFirstSource,
                                                           iFramesOnly, vshPeriod);
}

ServerMediaSubsession *
MPEG1or2FileServerDemux::newAC3AudioServerMediaSubsession() {
    return MPEG1or2DemuxedServerMediaSubsession::createNew(*this, 0xBD, fReuseFirstSource);
    // because, in a VOB file, the AC3 audio has stream id 0xBD
}

MPEG1or2DemuxedElementaryStream *
MPEG1or2FileServerDemux::newElementaryStream(unsigned clientSessionId,
                                             u_int8_t streamIdTag) {
    MPEG1or2Demux *demuxToUse;
    if (clientSessionId == 0) {
        // 'Session 0' is treated especially, because its audio & video streams
        // are created and destroyed one-at-a-time, rather than both streams being
        // created, and then (later) both streams being destroyed (as is the case
        // for other ('real') session ids).  Because of this, a separate demux is
        // used for session 0, and its deletion is managed by us, rather than
        // happening automatically.
        if (fSession0Demux == NULL) {
            // Open our input file as a 'byte-stream file source':
            ByteStreamFileSource *fileSource
                    = ByteStreamFileSource::createNew(envir(), fFileName);
            if (fileSource == NULL) return NULL;
            fSession0Demux = MPEG1or2Demux::createNew(envir(), fileSource, False/*note!*/);
        }
        demuxToUse = fSession0Demux;
    } else {
        // First, check whether this is a new client session.  If so, create a new
        // demux for it:
        if (clientSessionId != fLastClientSessionId) {
            // Open our input file as a 'byte-stream file source':
            ByteStreamFileSource *fileSource
                    = ByteStreamFileSource::createNew(envir(), fFileName);
            if (fileSource == NULL) return NULL;

            fLastCreatedDemux = MPEG1or2Demux::createNew(envir(), fileSource, True);
            // Note: We tell the demux to delete itself when its last
            // elementary stream is deleted.
            fLastClientSessionId = clientSessionId;
            // Note: This code relies upon the fact that the creation of streams for
            // different client sessions do not overlap - so one "MPEG1or2Demux" is used
            // at a time.
        }
        demuxToUse = fLastCreatedDemux;
    }

    if (demuxToUse == NULL) return NULL; // shouldn't happen

    return demuxToUse->newElementaryStream(streamIdTag);
}


static Boolean getMPEG1or2TimeCode(FramedSource *dataSource,
                                   MPEG1or2Demux &parentDemux,
                                   Boolean returnFirstSeenCode,
                                   float &timeCode); // forward

static float MPEG1or2ProgramStreamFileDuration(UsageEnvironment &env,
                                               char const *fileName,
                                               unsigned &fileSize) {
    FramedSource *dataSource = NULL;
    float duration = 0.0; // until we learn otherwise
    fileSize = 0; // ditto

    do {
        // Open the input file as a 'byte-stream file source':
        ByteStreamFileSource *fileSource = ByteStreamFileSource::createNew(env, fileName);
        if (fileSource == NULL) break;
        dataSource = fileSource;

        fileSize = (unsigned) (fileSource->fileSize());
        if (fileSize == 0) break;

        // Create a MPEG demultiplexor that reads from that source.
        MPEG1or2Demux *baseDemux = MPEG1or2Demux::createNew(env, dataSource, True);
        if (baseDemux == NULL) break;

        // Create, from this, a source that returns raw PES packets:
        dataSource = baseDemux->newRawPESStream();

        // Read the first time code from the file:
        float firstTimeCode;
        if (!getMPEG1or2TimeCode(dataSource, *baseDemux, True, firstTimeCode)) break;

        // Then, read the last time code from the file.
        // (Before doing this, flush the demux's input buffers,
        //  and seek towards the end of the file, for efficiency.)
        baseDemux->flushInput();
        unsigned const startByteFromEnd = 100000;
        unsigned newFilePosition
                = fileSize < startByteFromEnd ? 0 : fileSize - startByteFromEnd;
        if (newFilePosition > 0) fileSource->seekToByteAbsolute(newFilePosition);

        float lastTimeCode;
        if (!getMPEG1or2TimeCode(dataSource, *baseDemux, False, lastTimeCode)) break;

        // Take the difference between these time codes as being the file duration:
        float timeCodeDiff = lastTimeCode - firstTimeCode;
        if (timeCodeDiff < 0) break;
        duration = timeCodeDiff;
    } while (0);

    Medium::close(dataSource);
    return duration;
}

#define MFSD_DUMMY_SINK_BUFFER_SIZE (6+65535) /* large enough for a PES packet */

class MFSD_DummySink : public MediaSink {
public:
    MFSD_DummySink(MPEG1or2Demux &demux, Boolean returnFirstSeenCode);

    virtual ~MFSD_DummySink();

    char watchVariable;

private:
    // redefined virtual function:
    virtual Boolean continuePlaying();

private:
    static void afterGettingFrame(void *clientData, unsigned frameSize,
                                  unsigned numTruncatedBytes,
                                  struct timeval presentationTime,
                                  unsigned durationInMicroseconds);

    void afterGettingFrame1();

private:
    MPEG1or2Demux &fOurDemux;
    Boolean fReturnFirstSeenCode;
    unsigned char fBuf[MFSD_DUMMY_SINK_BUFFER_SIZE];
};

static void afterPlayingMFSD_DummySink(MFSD_DummySink *sink); // forward
static float computeSCRTimeCode(MPEG1or2Demux::SCR const &scr); // forward

static Boolean getMPEG1or2TimeCode(FramedSource *dataSource,
                                   MPEG1or2Demux &parentDemux,
                                   Boolean returnFirstSeenCode,
                                   float &timeCode) {
    // Start reading through "dataSource", until we see a SCR time code:
    parentDemux.lastSeenSCR().isValid = False;
    UsageEnvironment &env = dataSource->envir(); // alias
    MFSD_DummySink sink(parentDemux, returnFirstSeenCode);
    sink.startPlaying(*dataSource,
                      (MediaSink::afterPlayingFunc *) afterPlayingMFSD_DummySink, &sink);
    env.taskScheduler().doEventLoop(&sink.watchVariable);

    timeCode = computeSCRTimeCode(parentDemux.lastSeenSCR());
    return parentDemux.lastSeenSCR().isValid;
}


////////// MFSD_DummySink implementation //////////

MFSD_DummySink::MFSD_DummySink(MPEG1or2Demux &demux, Boolean returnFirstSeenCode)
        : MediaSink(demux.envir()),
          watchVariable(0), fOurDemux(demux), fReturnFirstSeenCode(returnFirstSeenCode) {
}

MFSD_DummySink::~MFSD_DummySink() {
}

Boolean MFSD_DummySink::continuePlaying() {
    if (fSource == NULL) return False; // sanity check

    fSource->getNextFrame(fBuf, sizeof fBuf,
                          afterGettingFrame, this,
                          onSourceClosure, this);
    return True;
}

void MFSD_DummySink::afterGettingFrame(void *clientData, unsigned /*frameSize*/,
                                       unsigned /*numTruncatedBytes*/,
                                       struct timeval /*presentationTime*/,
                                       unsigned /*durationInMicroseconds*/) {
    MFSD_DummySink *sink = (MFSD_DummySink *) clientData;
    sink->afterGettingFrame1();
}

void MFSD_DummySink::afterGettingFrame1() {
    if (fReturnFirstSeenCode && fOurDemux.lastSeenSCR().isValid) {
        // We were asked to return the first SCR that we saw, and we've seen one,
        // so we're done.  (Handle this as if the input source had closed.)
        onSourceClosure();
        return;
    }

    continuePlaying();
}

static void afterPlayingMFSD_DummySink(MFSD_DummySink *sink) {
    // Return from the "doEventLoop()" call:
    sink->watchVariable = ~0;
}

static float computeSCRTimeCode(MPEG1or2Demux::SCR const &scr) {
    double result = scr.remainingBits / 90000.0 + scr.extension / 300.0;
    if (scr.highBit) {
        // Add (2^32)/90000 == (2^28)/5625
        double const highBitValue = (256 * 1024 * 1024) / 5625.0;
        result += highBitValue;
    }

    return (float) result;
}
